#include "MiniWindow.h"
#include "ui_MiniWindow.h"
#include <QStringList>
#include <QString>
#include <iostream>
#include <ctime>

#include "Test.h"

using namespace std;

MiniWindow::MiniWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MiniWindow)
{
    // 初始化布局
    initUI();

    // 测试
    AllTest();
}

MiniWindow::~MiniWindow()
{
    delete ui;
}

void MiniWindow::initUI()
{
    ui->setupUi(this);

    // 设置初始选项
    ui->actionFill->trigger();
    ui->actionControlPoints->trigger();

    ui->tableWidget->setRowCount(5);
    ui->tableWidget->setColumnCount(1);

    QStringList horizonStr;
    horizonStr << tr("");
    ui->tableWidget->setHorizontalHeaderLabels(horizonStr);

    QStringList verticalStr;
    verticalStr << tr("类型") << tr("包围盒");
    ui->tableWidget->setVerticalHeaderLabels(verticalStr);

    this->setCentralWidget(ui->openGLWidget);
}

void MiniWindow::on_actionLine_triggered()
{
    ui->actionFill->setChecked(false);
    ui->openGLWidget->setWireframe(true);
}

void MiniWindow::on_actionFill_triggered()
{
    ui->openGLWidget->setWireframe(false);
    ui->actionLine->setChecked(false);
}

void MiniWindow::on_actionControlPoints_triggered(bool checked)
{
    ui->openGLWidget->setControlPoints(!checked);
}

MiniHandler *MiniWindow::registerObject(MiniObject* obj)
{
    return ui->openGLWidget->registerObject(obj);
}

void MiniWindow::testCurveMarchProjection()
{
    // 参数曲面
    auto f = [](float u, float v) -> Vector3f
    {
        return Vector3f{u, sin(u + v) * cos(u - v), v};
    };

    // 曲面上的曲线
    auto g = [=](float t) -> Vector3f
    {
        return f(2 * sin(t), 2 * cos(t));
    };

    int NN = 1000;
    const Real PI = 3.1415926535;
    MiniArray<Vector3f> points2(NN);
    for (int i = 0; i < NN; i++)
    {
        points2[i] = g(2 * PI / (NN-1) * i);
    }

    // 推入曲面上更多的点
    int um = 10;
    int vm = 8;
    MiniArray<Real> u((um + 1) * (vm + 1));
    MiniArray<Real> v((um + 1) * (vm + 1));

    // 中间洞上取的点被排除
    for (int i = 0; i < um + 1; i++)
    {
        for (int j = 0; j < vm + 1; j++)
        {
            // 记录每个点的 u,v 参数值
            int index = i * (vm + 1) + j;
            u[index] = -4 + 8.0 / um * i;
            v[index] = -4 + 8.0 / vm * j;

            Vector3f P = {u[index], v[index], 0};
            if (distance(P, {0, 0, 0}) < 2)
                continue;
            points2.push_back(f(u[index], v[index]));
        }
    }

    // 建立点云
    MiniCloud *cloud = new MiniCloud(points2);
    registerObject(cloud);
    MiniArray<Vector3f> Box = OrientBoundingBox(points2);

    // 最长的两个方向，构造初始投影平面
    MiniArray<Vector3f> oxy(3);
    oxy[0] = Box.back() + Box[1] + Box[3];
    oxy[1] = normalize(Box[0]);
    oxy[2] = normalize(Box[2]);
    // 注意两个方向的先后顺序会决定光照时的法向

    // 计算投影参数
    int N = points2.size();
    MiniArray<Real> parau(N);
    MiniArray<Real> parav(N);

    for (int i = 0; i < N; i++)
    {
        parau[i] = dot(points2[i] - oxy[0], oxy[1]);
        parav[i] = dot(points2[i] - oxy[0], oxy[2]);
    }

    // 标准化到 [0,1] 上，由于这时 u,v 参数不是分别递增的，所以需要寻找最大最小值
    parau = Normalized(parau);
    parav = Normalized(parav);

    // 权重向量
    Real kappa = 0.5;
    MiniArray<Real> w(N);
    for (int i = 0; i < N; i++)
    {
        if (i < 1000)
            w[i] = 1 - kappa;
        else
            w[i] = kappa;
    }

    Real alpha = 1e-6;
    Real beta = 1e-3;

    int Nu = 8;
    int Nv = 8;
    int p = 3;
    int q = 3;

    int times = 4;
    MiniBSplineSurface *S;
    MiniParaCurve *curve = new MiniParaCurve(g, 0, 2 * PI);
    for (int k = 0; k < times; k++)
    {
        S = cloud->ChordFittingSurface(parau, parav, Nu, Nv, p, q, w, alpha, beta);

        // 计算误差
        Real err = 0;
        for (int i = 0; i < N; i++)
        {
            double e = length(S->SurfacePoint(parau[i], parav[i]) - points2[i]);
            err += e * e / N;
        }
        const Real PI = 3.1415926535;

        clock_t start = clock();

        auto G = curve->CurveOthroProjection(S, 0, 2 * PI, NN, 1e-8);
//        auto G = curve->CurveMarchProjection(S, 0, 2 * PI, NN, 1e-12);

        double parae = 0;
        for (int i = 0; i < N; i++)
        {
            Vector uv(2);
            if (i < NN)
                uv = G[i];
            else
                uv = S->OrthoPointProjection(points2[i], {parau[i], parav[i]}, 1e-6);

            parae += fabs(parau[i] - uv[0]) + fabs(parav[i] - uv[1]);

            parau[i] = uv[0];
            parav[i] = uv[1];
        }

        double time = double(clock() - start) / CLOCKS_PER_SEC;
        qDebug() << "Projection Time: " << time;

        qDebug() << "Square Distance Error: " << err;
        qDebug() << "Parameter Error: " << parae / N;
        qDebug() << "Nu = " << Nu << ", Nv = " << Nv;
        qDebug() << "----------------------------------";

        Nu *= 2;
        Nv *= 2;

        if (k < times - 1)
            delete S;
    }
    delete curve;

    registerObject(S);
}

void MiniWindow::testDBS()
{
    // 参数曲面
//    auto f = [](float u, float v) -> Vector3f
//    {
//        return Vector3f{u, sin(u + v) * cos(u - v), v};
//    };

    const Real PI = 3.1415926535;
    auto f = [=](float u, float v) -> Vector3f
    {
        return Vector3f{sin(PI / 5 * u), cos(PI / 5 * u), v};
    };

//    auto f = [=](float u, float v) -> Vector3f
//    {
//        Real r = 1;
//        return Vector3f{r * cos(PI / 8 * u) * cos(PI / 8 * v),
//                    r * cos(PI / 8 * u) * sin(PI / 8 * v),
//                    r * sin(PI / 8 * u)};
//    };

    MiniArray<Vector3f> points;

    // 推入曲面上更多的点
    int um = 40;
    int vm = 40;
    MiniArray<Real> u((um + 1) * (vm + 1));
    MiniArray<Real> v((um + 1) * (vm + 1));

    // 中间洞上取的点被排除
    for (int i = 0; i < um + 1; i++)
    {
        for (int j = 0; j < vm + 1; j++)
        {
            // 记录每个点的 u,v 参数值
            int index = i * (vm + 1) + j;
            u[index] = -4 + 8.0 / um * i;
            v[index] = -4 + 8.0 / vm * j;

//            Vector3f P = {u[index], v[index], 0};
//            if (distance(P, {0, 0, 0}) < 1)
//                continue;

            points.push_back(f(u[index], v[index]));
        }
    }

    // 建立点云
    MiniCloud *cloud = new MiniCloud(points);
    registerObject(cloud);

    int Nu = 10;
    int Nv = 10;
    MiniBSplineSurface *surface = cloud->SDEInitialDBS(Nu, Nv, 3, 3, 1e-5, 1.0, 1.6);
    registerObject(surface);
}

void MiniWindow::AllTest()
{
    MiniArray<Vector3f> points;

    int M;
//    points = testBasisCircle(M);
//    points = testTirBoundary(M);
//    points = testRectBoundary(M);
//    points = testSixBoundary(M);

//    points = testFourArcBoundary(M);
    points = testSixArcBoundary(M);
//    points = testSemiBoundary(M);

    int N = points.size();

    int Nu = 8;
    int Nv = 8;
    int p = 3;
    int q = 3;
    Real alpha = 1e-6;
    Real beta = 1e-3;

    // 边界点和周围点加权
    Real kappa = 1e-3;
    MiniArray<Real> w(N);
    for (int i = 0; i < N; i++)
    {
        if (i < M)
            w[i] = 1 - kappa;
        else
            w[i] = kappa;
    }

    // uv 参数
    MiniArray<Real> U(N);
    MiniArray<Real> V(N);

    MiniCloud *cloud = new MiniCloud(points);

    clock_t start = clock();
    MiniBSplineSurface *uv = cloud->BoundaryParameterization(U, V, Nu, Nv, p, q, w, alpha, beta);
    double t = double(clock() - start) / CLOCKS_PER_SEC;	// 获取时间间隔
    qDebug() << "Time: " << t;

    registerObject(cloud);
    registerObject(uv);
}
